﻿using System;
using System.Collections.Generic;
using System.Linq;
using DependencyAnalysis.DataModel;

namespace DependencyAnalysis.Serialization
{
    public class MetricsReport : IReport
    {
        public Setup Setup { get; set; }

        public IReportObject FetchReport<T>() where T : IReportObject, new()
        {
            if (Setup == null) throw new InvalidOperationException();
            dynamic report = new T { TypeName = "Report", Name = "Metrics Summary" };

            var moduleOut = Setup.Model.DependenciesPerModuleOut;
            report.Modules = FetchModules<T>(moduleOut);

            return report;
        }

        private dynamic FetchModules<T>(IDictionary<IDescriptor, ISet<DependencyDescriptor>> moduleOut) where T : IReportObject, new()
        {
            dynamic modules = new List<T>();
            foreach (ModuleDescriptor module in moduleOut.Keys)
            {
                if (!module.IsPartOfProject) continue;
                dynamic xmlModule = new T { TypeName = "Module", Name = module.FullName };
                xmlModule.DistanceFromMainSequence = Setup.Metrics.DistanceFromMainSequence(module);
                xmlModule.Instability = Setup.Metrics.Instability(module);
                xmlModule.Abstractness = Setup.Metrics.Abstractness(module);
                xmlModule.RelationalCohesion = Setup.Metrics.RelationalCohesion(module);
                xmlModule.AfferentCoupling = Setup.Metrics.AfferentCoupling(module);
                xmlModule.EfferentCoupling = Setup.Metrics.EfferentCoupling(module);
                xmlModule.NumberOfTypes = module.Submodules.SelectMany(x => x.Types).Count();
                xmlModule.Identifier = module.Identifier;

                xmlModule.Submodules = FetchSubmodules<T>(module);
                modules.Add(xmlModule);
            }
            return modules;
        }

        private List<T> FetchSubmodules<T>(ModuleDescriptor module) where T : IReportObject, new()
        {
            var submodules = new List<T>();
            foreach (var submodule in module.Submodules)
            {
                dynamic xmlSubmodule = new T { TypeName = "Submodule", Name = submodule.FullName };
                xmlSubmodule.DistanceFromMainSequence = Setup.Metrics.DistanceFromMainSequence(submodule);
                xmlSubmodule.Instability = Setup.Metrics.Instability(submodule);
                xmlSubmodule.Abstractness = Setup.Metrics.Abstractness(submodule);
                xmlSubmodule.RelationalCohesion = Setup.Metrics.RelationalCohesion(submodule);
                xmlSubmodule.AfferentCoupling = Setup.Metrics.AfferentCoupling(submodule);
                xmlSubmodule.EfferentCoupling = Setup.Metrics.EfferentCoupling(submodule);
                xmlSubmodule.NumberOfTypes = submodule.Types.Count;
                xmlSubmodule.Identifier = submodule.Identifier;

                xmlSubmodule.Types = FetchTypes<T>(submodule);
                submodules.Add(xmlSubmodule);
            }
            return submodules;
        }

        private List<T> FetchTypes<T>(SubmoduleDescriptor submodule) where T : IReportObject, new()
        {
            var types = new List<T>();
            foreach (var type in submodule.Types)
            {
                dynamic xmlType = new T { TypeName = "Type", Name = type.Name };
                xmlType.LackOfCohesion = Setup.Metrics.LackOfCohesion(type);
                xmlType.NumberOfFields = Setup.Metrics.NumberOfFields(type);
                xmlType.NumberOfInterfaces = Setup.Metrics.NumberOfInterfaces(type);
                xmlType.NumberOfInstructions = Setup.Metrics.NumberOfInstructions(type);
                xmlType.NumberOfMethods = Setup.Metrics.NumberOfMethods(type);
                xmlType.NumberOfChildren = Setup.Metrics.NumberOfChildren(type);
                xmlType.NumberOfOverriddenMethods = Setup.Metrics.NumberOfOverriddenMethods(type);
                xmlType.CyclomaticComplexity = Setup.Metrics.CyclomaticComplexity(type);
                xmlType.SpecializationIndex = Setup.Metrics.SpecializationIndex(type);
                xmlType.DepthOfInheritance = Setup.Metrics.DepthOfInheritance(type);
                xmlType.AssociationBetweenClasses = Setup.Metrics.AssociationBetweenClasses(type);
                xmlType.AfferentCoupling = Setup.Metrics.AfferentCoupling(type);
                xmlType.EfferentCoupling = Setup.Metrics.EfferentCoupling(type);
                xmlType.Identifier = type.Identifier;

                xmlType.Members = FetchMembers<T>(type);
                types.Add(xmlType);
            }
            return types;
        }

        private List<T> FetchMembers<T>(TypeDescriptor type) where T : IReportObject, new()
        {
            var members = new List<T>();
            foreach (var member in type.Members)
            {
                dynamic xmlMember = new T { TypeName = "Member", Name = member.Name };
                xmlMember.CyclomaticComplexity = Setup.Metrics.CyclomaticComplexity(member);
                xmlMember.NumberOfInstructions = Setup.Metrics.NumberOfInstructions(member);
                xmlMember.NestingDepth = Setup.Metrics.NestingDepth(member);
                xmlMember.NumberOfParameters = Setup.Metrics.NumberOfParameters(member);
                xmlMember.NumberOfVariables = Setup.Metrics.NumberOfVariables(member);
                xmlMember.AfferentCoupling = Setup.Metrics.AfferentCoupling(member);
                xmlMember.EfferentCoupling = Setup.Metrics.EfferentCoupling(member);
                xmlMember.Identifier = member.Identifier;

                members.Add(xmlMember);
            }
            return members;
        }
    }
}